use crate::access::Access;
use crate::attr::{AttrConstant, Attribute};
use crate::constant::Constant;
use crate::reader::Stream;
use crate::value::Value;
use crate::Config;

#[derive(Debug)]
pub struct Field {
	pub(crate) access: Access,
	pub(crate) name: String,
	pub(crate) sign_java: String,
	pub(crate) sign: Value,
	pub(crate) constant: Option<AttrConstant>,
}
impl Field {
	pub fn parse(s: &mut Stream, constants: &Vec<Constant>) -> Option<Self> {
		let access = Access(s.read()?);
		let name_index: u16 = s.read()?;
		let name = constants.get(name_index as usize)?.to_string(constants)?;
		let sign_index: u16 = s.read()?;
		let sign_java = constants.get(sign_index as usize)?.to_string(constants)?;
		let sign = Value::from_str(&sign_java)?;
		let attr_count: u16 = s.read()?;
		let mut attrs = vec![];
		let mut constant: Option<AttrConstant> = None;
		for _ in 0..attr_count {
			let attr = Attribute::parse(s, constants)?;
			if let Some(v) = attr.to_constant() {
				constant = Some(v.clone());
			}
			attrs.push(attr);
		}
		Some(Self {
			access,
			name,
			sign_java,
			sign,
			constant,
		})
	}
	pub fn is_const(&self) -> bool {
		self.constant.is_some()
	}
	pub fn sign_u8(&self) -> String {
		format!("b\"{}\0\'", self.sign_java)
	}
	pub fn fmt_constant<'a>(&'a self, pubtag: &'a str) -> FmtConstant<'a> {
		FmtConstant(self, pubtag)
	}
	pub fn fmt_get<'a>(&'a self, pubtag: &'a str, class_fun: &'a str, cfg: &'a Config) -> FmtGet<'a> {
		FmtGet(self, pubtag, class_fun, cfg)
	}
	pub fn fmt_set<'a>(&'a self, pubtag: &'a str, class_fun: &'a str, cfg: &'a Config) -> FmtSet<'a> {
		FmtSet(self, pubtag, class_fun, cfg)
	}
	pub fn fmt_get_static<'a>(&'a self, pubtag: &'a str, class_fun: &'a str, cfg: &'a Config) -> FmtGetStatic<'a> {
		FmtGetStatic(self, pubtag, class_fun, cfg)
	}
	pub fn fmt_set_static<'a>(&'a self, pubtag: &'a str, class_fun: &'a str, cfg: &'a Config) -> FmtSetStatic<'a> {
		FmtSetStatic(self, pubtag, class_fun, cfg)
	}
}
pub struct FmtConstant<'a>(pub &'a Field, pub &'a str);
impl<'a> std::fmt::Display for FmtConstant<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		if let Some(constant) = &self.0.constant {
			let tag = format!("{}", self.0.sign.fmt_const_tag());
			let name = casbab::screaming_snake(&self.0.name);
			f.write_fmt(format_args!(
				"{pubtag}const {name}:{tag} = {value};",
				value = constant.fmt_value(&tag),
				pubtag = self.1
			))?;
		}
		Ok(())
	}
}

pub struct FmtGet<'a>(pub &'a Field, pub &'a str, pub &'a str, pub &'a Config);
impl<'a> std::fmt::Display for FmtGet<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		if self.0.access.is_static() {
			return Ok(());
		}
		f.write_fmt(format_args!(
			r##"
{pubtag}fn r#{rust_name}<'a>(&'a self, env: &'a JEnv) -> Option<{rust_ret}> {{
	static CACHE: CachedID = CachedID::new();
	CACHE
		.get(|| Self::{class_fun}(Some(env))?.field(env, b"{name}\0", b"{sign_java}\0"))?
		.get(env, self.as_ref())
}}"##,
			name = self.0.name,
			rust_name = casbab::snake(&self.0.name),
			sign_java = self.0.sign_java,
			rust_ret = self.0.sign.fmt_jnim_type(true, self.3),
			pubtag = self.1,
			class_fun = self.2,
		))
	}
}
pub struct FmtSet<'a>(pub &'a Field, pub &'a str, pub &'a str, pub &'a Config);
impl<'a> std::fmt::Display for FmtSet<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		if self.0.access.is_static() {
			return Ok(());
		}
		f.write_fmt(format_args!(
			r##"
{pubtag}fn r#{rust_name}_<'a>(&'a self, env: &'a JEnv, value: {rust_type}) -> Option<()> {{
	static CACHE: CachedID = CachedID::new();
	CACHE
		.get(|| Self::{class_fun}(Some(env))?.field(env, b"{name}\0", b"{sign_java}\0"))?
		.set(env, self.as_ref(), value)
}}"##,
			name = self.0.name,
			rust_name = casbab::snake(&self.0.name),
			sign_java = self.0.sign_java,
			rust_type = self.0.sign.fmt_jnim_type(false, self.3),
			pubtag = self.1,
			class_fun = self.2,
		))
	}
}

pub struct FmtGetStatic<'a>(pub &'a Field, pub &'a str, pub &'a str, pub &'a Config);
impl<'a> std::fmt::Display for FmtGetStatic<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		if !self.0.access.is_static() {
			return Ok(());
		}
		f.write_fmt(format_args!(
			r##"
{pubtag}fn r#{rust_name}<'a>(env: &'a JEnv) -> Option<{rust_ret}> {{
	let class = Self::{class_fun}(Some(env))?;
	static CACHE: CachedID = CachedID::new();
	CACHE
		.get(|| class.field_static(env, b"{name}\0", b"{sign_java}\0"))?
		.get(env, class)
}}"##,
			name = self.0.name,
			rust_name = casbab::snake(&self.0.name),
			sign_java = self.0.sign_java,
			rust_ret = self.0.sign.fmt_jnim_type(true, self.3),
			pubtag = self.1,
			class_fun = self.2,
		))
	}
}
pub struct FmtSetStatic<'a>(pub &'a Field, pub &'a str, pub &'a str, pub &'a Config);
impl<'a> std::fmt::Display for FmtSetStatic<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		if !self.0.access.is_static() {
			return Ok(());
		}
		f.write_fmt(format_args!(
			r##"
{pubtag}fn r#{rust_name}_<'a>(env: &'a JEnv, value: {rust_type}) -> Option<()> {{
	let class = Self::{class_fun}(Some(env))?;
	static CACHE: CachedID = CachedID::new();
	CACHE
		.get(|| class.field_static(env, b"{name}\0", b"{sign_java}\0"))?
		.set(env, class, value)
}}"##,
			name = self.0.name,
			rust_name = casbab::snake(&self.0.name),
			sign_java = self.0.sign_java,
			rust_type = self.0.sign.fmt_jnim_type(false, self.3),
			pubtag = self.1,
			class_fun = self.2,
		))
	}
}
